Plant Seedlings Image Classification using CNNs in Keras

Data Description:
There is provided a dataset of images of plant seedlings at various stages of grown. Each image has a filename that is its unique id. The dataset comprises 12 plant species. The goal of the project is to create a classifier capable of determining a plant's species from a photo.

Objective:
To implement the techniques learnt as a part of the course
Dataset:
The dataset was download from Olympus to Google Colab.
The data file names are:

  • images.npy
  • Label.csv

Learning Outcomes:
1.Pre-processing of image data.
2.Visualization of images.
3.Building CNN.
4.Evaluate the Model.

In [1]:
from google.colab import drive
drive.mount('/content/drive')
Mounted at /content/drive

Import the libraries, load dataset, print shape of data, visualize the images in dataset

In [2]:
#importing all the necessary modules:
import cv2  # IMAGE PROCESSING - OPENCV
import math # MATHEMATICAL OPERATIONS
import numpy as np # MATRIX OPERATIONS
import pandas as pd # EFFICIENT DATA STRUCTURES
import matplotlib.pyplot as plt # GRAPHING AND VISUALIZATIONS
%matplotlib inline
from glob import glob # FILE OPERATIONS
import seaborn as sns
import cv2
import os
import itertools
import tensorflow as tf

from google.colab.patches import cv2_imshow
from tqdm import tqdm

# SKLEARN MODULES
from sklearn.metrics import confusion_matrix
from sklearn.model_selection import train_test_split
from sklearn.metrics import classification_report, confusion_matrix



# KERAS MODULES
from keras.utils.np_utils import to_categorical  #convert to one-hot-encoding
from keras.models import Sequential, load_model
from keras.layers import Dense, Dropout, Flatten, Conv2D, MaxPool2D,MaxPooling2D, BatchNormalization
from keras.optimizers import Adam
from keras.preprocessing.image import ImageDataGenerator
from keras.callbacks import EarlyStopping,ModelCheckpoint, ReduceLROnPlateau

Load the data

In [ ]:
from google.colab import drive
drive.mount("/content/drive", force_remount=True)
Mounted at /content/drive
In [3]:
data = np.load('/content/drive/My Drive/Plants/plant-seedlings-classification/images.npy')
In [4]:
data
Out[4]:
array([[[[ 35,  52,  78],
         [ 36,  49,  76],
         [ 31,  45,  69],
         ...,
         [ 78,  95, 114],
         [ 76,  93, 110],
         [ 80,  95, 109]],

        [[ 33,  46,  68],
         [ 37,  50,  73],
         [ 48,  65,  83],
         ...,
         [ 81,  96, 113],
         [ 74,  89, 105],
         [ 83,  95, 109]],

        [[ 34,  50,  68],
         [ 35,  52,  72],
         [ 70,  85, 101],
         ...,
         [ 83,  97, 112],
         [ 79,  94, 108],
         [ 79,  94, 107]],

        ...,

        [[ 35,  50,  69],
         [ 42,  57,  73],
         [ 42,  57,  72],
         ...,
         [ 60,  76,  92],
         [ 67,  81,  97],
         [ 64,  77,  95]],

        [[ 36,  52,  67],
         [ 48,  63,  78],
         [ 41,  57,  73],
         ...,
         [ 44,  66,  83],
         [ 58,  76,  91],
         [ 57,  74,  90]],

        [[ 44,  58,  70],
         [ 43,  57,  73],
         [ 40,  55,  72],
         ...,
         [ 41,  70,  92],
         [ 55,  78,  97],
         [ 61,  79,  96]]],


       [[[ 30,  47,  63],
         [ 30,  50,  60],
         [ 34,  47,  63],
         ...,
         [ 48,  59,  74],
         [ 42,  54,  69],
         [ 44,  56,  70]],

        [[ 30,  49,  67],
         [ 26,  47,  60],
         [ 30,  40,  61],
         ...,
         [ 50,  64,  76],
         [ 52,  67,  78],
         [ 45,  56,  72]],

        [[ 23,  46,  65],
         [ 27,  48,  64],
         [ 25,  40,  59],
         ...,
         [ 39,  59,  81],
         [ 47,  62,  79],
         [ 42,  54,  69]],

        ...,

        [[ 32,  54,  72],
         [ 58,  82,  95],
         [ 72,  96, 109],
         ...,
         [ 60,  80,  99],
         [ 50,  72,  92],
         [ 45,  64,  84]],

        [[ 31,  51,  67],
         [ 25,  50,  64],
         [ 38,  64,  80],
         ...,
         [ 63,  83, 101],
         [ 57,  78,  96],
         [ 50,  69,  89]],

        [[ 18,  32,  56],
         [ 16,  27,  50],
         [ 34,  49,  71],
         ...,
         [ 59,  84, 101],
         [ 55,  80,  97],
         [ 39,  59,  82]]],


       [[[154, 149, 144],
         [162, 156, 152],
         [161, 154, 151],
         ...,
         [175, 180, 183],
         [177, 181, 184],
         [175, 179, 179]],

        [[154, 150, 147],
         [162, 156, 154],
         [154, 147, 145],
         ...,
         [168, 173, 180],
         [171, 175, 181],
         [173, 176, 180]],

        [[151, 149, 149],
         [159, 155, 156],
         [158, 154, 152],
         ...,
         [173, 176, 180],
         [178, 182, 186],
         [179, 182, 185]],

        ...,

        [[ 58,  72,  96],
         [ 69,  83, 105],
         [ 69,  84, 104],
         ...,
         [175, 170, 167],
         [176, 169, 167],
         [176, 167, 165]],

        [[ 64,  76,  98],
         [ 77,  87, 108],
         [ 79,  91, 109],
         ...,
         [177, 172, 170],
         [179, 173, 171],
         [176, 169, 166]],

        [[ 68,  80,  99],
         [ 78,  89, 108],
         [ 79,  92, 108],
         ...,
         [179, 173, 170],
         [177, 171, 166],
         [178, 171, 167]]],


       ...,


       [[[163, 165, 170],
         [ 55,  56,  67],
         [ 58,  56,  60],
         ...,
         [ 46,  47,  58],
         [ 49,  51,  60],
         [ 53,  54,  64]],

        [[101,  94, 120],
         [ 60,  58,  68],
         [ 57,  58,  62],
         ...,
         [ 47,  48,  58],
         [ 55,  57,  65],
         [ 50,  54,  64]],

        [[ 64,  56,  76],
         [ 64,  60,  67],
         [ 54,  50,  55],
         ...,
         [ 47,  50,  60],
         [ 48,  51,  62],
         [ 45,  49,  60]],

        ...,

        [[ 30,  43,  64],
         [ 43,  60,  86],
         [ 82, 102, 125],
         ...,
         [ 42,  58,  76],
         [ 44,  59,  79],
         [ 48,  64,  81]],

        [[ 36,  47,  62],
         [ 27,  41,  64],
         [ 42,  59,  89],
         ...,
         [ 42,  60,  76],
         [ 41,  59,  77],
         [ 42,  60,  76]],

        [[ 30,  41,  57],
         [ 26,  40,  59],
         [ 30,  45,  68],
         ...,
         [ 41,  59,  74],
         [ 39,  60,  73],
         [ 40,  57,  74]]],


       [[[116, 142, 153],
         [107, 137, 152],
         [110, 141, 154],
         ...,
         [ 50,  74,  92],
         [ 55,  78,  96],
         [ 49,  70,  91]],

        [[101, 128, 138],
         [ 95, 128, 145],
         [115, 145, 154],
         ...,
         [ 46,  69,  86],
         [ 47,  70,  91],
         [ 48,  71,  90]],

        [[ 47,  61,  78],
         [ 77, 100, 117],
         [101, 132, 140],
         ...,
         [ 25,  36,  67],
         [ 40,  61,  87],
         [ 44,  71,  89]],

        ...,

        [[ 42,  58,  79],
         [ 44,  57,  77],
         [ 39,  54,  72],
         ...,
         [ 54,  71,  93],
         [ 54,  74,  90],
         [ 41,  56,  73]],

        [[ 38,  63,  92],
         [ 34,  60,  87],
         [ 40,  62,  81],
         ...,
         [ 59,  75,  94],
         [ 50,  70,  88],
         [ 40,  57,  73]],

        [[ 33,  61,  93],
         [ 33,  62,  95],
         [ 42,  70,  94],
         ...,
         [ 52,  67,  89],
         [ 49,  67,  87],
         [ 34,  51,  71]]],


       [[[ 53,  66,  84],
         [ 45,  56,  74],
         [ 52,  67,  86],
         ...,
         [ 50,  71,  95],
         [ 83, 106, 124],
         [ 65,  86, 113]],

        [[ 54,  68,  87],
         [ 38,  48,  63],
         [ 29,  44,  67],
         ...,
         [ 63,  85, 105],
         [ 64,  89, 112],
         [ 71,  95, 115]],

        [[ 49,  63,  80],
         [ 60,  80, 104],
         [ 78, 107, 127],
         ...,
         [ 41,  70,  73],
         [ 42,  77,  80],
         [ 57,  83,  95]],

        ...,

        [[ 50,  65,  79],
         [ 50,  65,  84],
         [ 55,  74,  94],
         ...,
         [ 28,  52,  79],
         [ 31,  50,  74],
         [ 32,  51,  72]],

        [[ 59,  75,  89],
         [ 57,  74,  91],
         [ 57,  79,  96],
         ...,
         [ 34,  56,  79],
         [ 71,  90, 102],
         [ 72,  89, 104]],

        [[ 68,  85, 103],
         [ 66,  80,  96],
         [ 67,  86, 100],
         ...,
         [ 70,  87, 101],
         [ 68,  82,  95],
         [ 68,  84, 101]]]], dtype=uint8)
In [5]:
print("Shape of Images:", data.shape)
Shape of Images: (4750, 128, 128, 3)
In [6]:
data1 = pd.read_csv('/content/drive/My Drive/Plants/plant-seedlings-classification/Labels.csv')
In [7]:
print("Shape of Labels:", data1.shape)
Shape of Labels: (4750, 1)
In [8]:
base_path='/content/drive/My Drive/Plants'
save_extracted='/content/drive/My Drive/Plants/Save/'
In [9]:
np.save(save_extracted+'labels.npy', data1)
In [10]:
np.save(save_extracted+'images.npy', data)
In [11]:
TrainImages=np.load(save_extracted+'images.npy', False, True)
TrainLabels=np.load(save_extracted+'labels.npy', False, True)
In [12]:
print(f"Training image array shape:{TrainImages.shape}")
print(f"Training target labels:{TrainLabels.shape}")
Training image array shape:(4750, 128, 128, 3)
Training target labels:(4750, 1)
In [54]:
# visualize the images in dataset
fig=plt.figure(figsize=(30, 35))
columns = 12
rows = 12
for i in range(1, columns*rows +1):
    img = TrainImages[i]
    label = TrainLabels[i][0]
    #print(f'Image name:{label}')
    fig.add_subplot(rows, columns, i)
    plt.imshow(img)
plt.show(block=True)
In [57]:
# Check Images and labels by choosing each time the value of i
fig=plt.figure(figsize=(10, 10))

i = 5
img = TrainImages[i]
label = TrainLabels[i][0]
print(f'Image name:{label}')
plt.imshow(img)


plt.show(block=True)
Image name:Small-flowered Cranesbill

Few training image has less quality, but it might overcome in pre-processing

In [59]:
# Aply Sobel filter to justify image quality
fig=plt.figure(figsize=(10, 10))
sobel = cv2.Sobel(img, cv2.CV_64F, 1, 1, ksize=5)
plt.imshow(sobel)
Clipping input data to the valid range for imshow with RGB data ([0..1] for floats or [0..255] for integers).
Out[59]:
<matplotlib.image.AxesImage at 0x7f4ae8809f98>

Pre-processing

Normalize the Data

  • The Data (TrainImages) needs to be normalized to 0-1 by diving the values by 255
In [60]:
TrainImages = TrainImages.astype('float32')
TrainImages /= 255
# Check the nomalized data
print(f'Shape of the Train array:{TrainImages.shape}')
print(f'Minimum value in the Train Array:{TrainImages.min()}')
print(f'Maximum value in the Train Array:{TrainImages.max()}')
Shape of the Train array:(4750, 128, 128, 3)
Minimum value in the Train Array:0.0
Maximum value in the Train Array:1.0

Split the dataset

Split the dataset into training, testing, and validation set. (Hint: First split train images and train labels into training and testing set with test_size = 0.3. Then further split test data into test and validation set with test_size = 0.5)

In [61]:
# Step#1: Split train and test set
X_train, X_test, y_train, y_test = train_test_split(TrainImages, TrainLabels, test_size=0.3, random_state=42)
X_train.shape, X_test.shape
Out[61]:
((3325, 128, 128, 3), (1425, 128, 128, 3))
In [62]:
# Step#2: Split validation from test set
X_test, X_validation, y_test, y_validation = train_test_split(X_test, y_test, test_size=0.5, random_state=42)
X_test.shape, X_validation.shape
Out[62]:
((712, 128, 128, 3), (713, 128, 128, 3))

One Hot encoding to target values

In [63]:
from sklearn.preprocessing import LabelBinarizer
encoder = LabelBinarizer()
y_train = encoder.fit_transform(y_train)
y_test = encoder.fit_transform(y_test)
y_validation = encoder.fit_transform(y_validation)

There is an alternate way to convert the target variable to one-hot

  1. Convert String categorical to numeric
  2. Use tensorflow.keras.utils.to_categorical to convert to binary array
In [64]:
# Display target variable
y_train[0]
Out[64]:
array([0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0])

Gaussian Blurring: Image blurring is achieved by convolving the image with a low-pass filter kernel. It is useful for removing noise. It actually removes high frequency content (e.g: noise, edges) from the image resulting in edges being blurred when this is filter is applied.

In [66]:
# Preview the image before Gaussian Blur
plt.imshow(X_train[10], cmap='gray')
Out[66]:
<matplotlib.image.AxesImage at 0x7f4aeda0bf60>
In [67]:
plt.imshow(cv2.GaussianBlur(X_train[10], (15,15), 0))
Out[67]:
<matplotlib.image.AxesImage at 0x7f4ae851b278>
In [68]:
# Now we apply the gaussian blur to each 128x128 pixels array (image) to reduce the noise in the image
for idx, img in enumerate(X_train):
  X_train[idx] = cv2.GaussianBlur(img, (5, 5), 0)
In [69]:
# Preview the image after Gaussian Blur
plt.imshow(X_train[10], cmap='gray')
Out[69]:
<matplotlib.image.AxesImage at 0x7f4ae85fe940>
In [70]:
# Gaussian Blue to Test and Validation sets
for idx, img in enumerate(X_test):
  X_test[idx] = cv2.GaussianBlur(img, (5, 5), 0)

for idx, img in enumerate(X_validation):
  X_validation[idx] = cv2.GaussianBlur(img, (5, 5), 0)
In [71]:
# visualize the images in dataset after pre-processing
fig=plt.figure(figsize=(20, 22))
columns = 8
rows = 8
for i in range(1, columns*rows +1):
    img = TrainImages[i]
    label = TrainLabels[i][0]
    #print(f'Image name:{label}')
    fig.add_subplot(rows, columns, i)
    plt.imshow(img)
plt.show(block=True)
In [82]:
print(TrainImages.shape)
print(TrainImages.size)
print(TrainImages.ndim)
print(len(TrainImages))
(4750, 128, 128, 3)
233472000
4
4750

Compatibility of input data with the Keras Model

You always have to give a 4D array as input to the CNN. So input data has a shape of (batch_size, height, width, depth), where the first dimension represents the batch size of the image and the other three dimensions represent dimensions of the image which are height, width, and depth. The output of the CNN is also a 4D array. Where batch size would be the same as input batch size but the other 3 dimensions of the image might change depending upon the values of filter, kernel size, and padding we use.
In our case we are training images with 4D model with the shape (4750, 128, 128, 3) for input and the output size which is 128x128. When the model is trained with outputsize as 128x128, then the predicted size is also 128x128.

Check main_model.summary below, then you can see that output of main_model is (conv2d (Conv2D) (None, 128, 128, 32))

As you can notice the output shape is ((None, 128, 128, 32). The first dimension represents the batch size, which is None at the moment. Because the network does not know the batch size in advance. Once you fit the data, None would be replaced by the batch size you give while fitting the data. So our data is compatible with Keras model.

CNN Model Architecture

The Sequential model is a linear stack of layers. The first layer in the model needs to receive information about its input shape and the following layers will do automatic shape reference. The convolutional layer is the core building block of a CNN. The layer's parameters consist of a set of learnable filters. During the forward pass, each filter is convolved across the image to produce a 2D activation map of each filter. Then stacking the activation maps for each filter forms the full output of the convolutional layer. The Batch normalization layer normalizes the activations of the convolutional layers by applying a transformation that maintains the mean close to 0 and the standard deviation close to 1. The max pooling layer serves as a form of non-linear downsampling. In this case, the 2x2 filters compute the maximum value of four pixels and make a stride of 2 pixels (width and height) at each depth. There are a total of 6 convolutional layers and 3 max pooling layers. The flatten layer is used to convert the final feature map into a 1D vector, combining all of the features of the previous layer. In the final layer, I used softmax activation so the neural network outputs the probability distribution for each class.

For the convolutional and dense layers, I used the ReLU activation function. For training deep neural networks, ReLU is more effective than the sigmoid and tangent activation functions because it prevents gradients from saturating. The vanishing gradient problem causes the neural network to get stuck preventing meaningful learning from taking place.

Before training the model, we need to configure the learning process by specifying the optimizer, loss function, and list of metrics. The loss function measures the error rate between the model's predicted and observed labels. The categorical crossentropy loss function is computed by taking the average of all cross-entropies in the sample. It will measure the probability that the training sample belongs to an individual class. The cost function is the average of the loss function over a large number of training samples. The goal of the optimization algorithm is to minimize the cost function by iteratively updating the weights and biases. I used the Adam (short for Adaptive Moment Estimation) optimizer because it's effective and achieves good results quickly. For information about how the optimizer works, click here.

Creating the Model

Steps:

  1. Initialize CNN Classifier

  2. Add Convolution layer with 32 kernels of 3x3 shape

  3. Add Maxpooling layer of size 2x2

  4. Flatten the input array

  5. Add dense layer with relu activation function

  6. Dropout the probability

  7. Add softmax Dense layer as output

In [83]:
def create_model(input_shape, num_classes):
  # Initialize CNN Classified
  model = Sequential()

  # Add convolution layer with 32 filters and 3 kernels
  model.add(Conv2D(32, (3,3), input_shape=input_shape, padding='same', activation=tf.nn.relu))
  model.add(MaxPooling2D(pool_size=(2,2)))
  model.add(Dropout(rate=0.25))

  # Add convolution layer with 32 filters and 3 kernels
  model.add(Conv2D(filters=32, kernel_size=3, padding='same', activation=tf.nn.relu))
  model.add(Conv2D(filters=64, kernel_size=3, padding='same', activation=tf.nn.relu))
  model.add(MaxPooling2D(pool_size=(2,2)))
  model.add(Dropout(rate=0.25))

  # Add convolution layer with 32 filters and 3 kernels
  model.add(Conv2D(filters=32, kernel_size=3, padding='same', activation=tf.nn.relu))
  model.add(MaxPooling2D(pool_size=(2,2)))
  model.add(Dropout(rate=0.25))

  # Flatten the 2D array to 1D array
  model.add(Flatten())

  # Create fully connected layers with 512 units
  model.add(Dense(512, activation=tf.nn.relu))
  model.add(Dropout(0.5))


  # Adding a fully connected layer with 128 neurons
  model.add(Dense(units = 128, activation = tf.nn.relu))
  model.add(Dropout(0.5))

  # The final output layer with 12 neurons to predict the categorical classifcation
  model.add(Dense(units = num_classes, activation = tf.nn.softmax))
  return model

Sequential: Defines a Sequence of layers

Conv2D: Keras Conv2D is a 2D Convolution Layer, this layer creates a convolution kernel that is wind with layers input which helps produce a tensor of outputs.

MaxPool2D: The objective is to down-sample an input representation

Flatten: Convert the 2D to 1D array

Dense: Adds a layers of neurons

Activation Functions:

Relu: Relu effectively means "If X>0 return X, else return 0" -- so what it does it it only passes values 0 or greater to the next layer in the network.

Softmax: takes a set of values, and effectively picks the biggest one, so, for example, if the output of the last layer looks like [0.1, 0.1, 0.05, 0.1, 9.5, 0.1, 0.05, 0.05, 0.05], it saves you from fishing through it looking for the biggest value, and turns it into [0,0,0,0,1,0,0,0,0] -- The goal is to save a lot of coding!

In [106]:
class myCallback(tf.keras.callbacks.Callback):
  def on_epoch_end(self, epoch, logs={}):
    if(logs.get('accuracy')>0.95):
      print("\nReached 95% accuracy so cancelling training!")
      self.model.stop_training = True

callbacks = myCallback()

es = EarlyStopping(monitor='val_accuracy', mode='min', verbose=1, patience=10)
In [85]:
input_shape = X_train.shape[1:] # Input shape of X_train
num_classes = y_train.shape[1] # Target column size

model = create_model(input_shape, num_classes)
optimizer = tf.keras.optimizers.Adam(learning_rate=0.001) # Optimizer
# optimizer = tf.keras.optimizers.SGD(lr=1 * 1e-1, momentum=0.9, nesterov=True)

model.compile(optimizer=optimizer,
              loss='categorical_crossentropy',
              metrics=['accuracy'])

model.summary()
Model: "sequential"
_________________________________________________________________
Layer (type)                 Output Shape              Param #   
=================================================================
conv2d (Conv2D)              (None, 128, 128, 32)      896       
_________________________________________________________________
max_pooling2d (MaxPooling2D) (None, 64, 64, 32)        0         
_________________________________________________________________
dropout (Dropout)            (None, 64, 64, 32)        0         
_________________________________________________________________
conv2d_1 (Conv2D)            (None, 64, 64, 32)        9248      
_________________________________________________________________
conv2d_2 (Conv2D)            (None, 64, 64, 64)        18496     
_________________________________________________________________
max_pooling2d_1 (MaxPooling2 (None, 32, 32, 64)        0         
_________________________________________________________________
dropout_1 (Dropout)          (None, 32, 32, 64)        0         
_________________________________________________________________
conv2d_3 (Conv2D)            (None, 32, 32, 32)        18464     
_________________________________________________________________
max_pooling2d_2 (MaxPooling2 (None, 16, 16, 32)        0         
_________________________________________________________________
dropout_2 (Dropout)          (None, 16, 16, 32)        0         
_________________________________________________________________
flatten (Flatten)            (None, 8192)              0         
_________________________________________________________________
dense (Dense)                (None, 512)               4194816   
_________________________________________________________________
dropout_3 (Dropout)          (None, 512)               0         
_________________________________________________________________
dense_1 (Dense)              (None, 128)               65664     
_________________________________________________________________
dropout_4 (Dropout)          (None, 128)               0         
_________________________________________________________________
dense_2 (Dense)              (None, 12)                1548      
=================================================================
Total params: 4,309,132
Trainable params: 4,309,132
Non-trainable params: 0
_________________________________________________________________
In [86]:
history = model.fit(X_train, y_train, validation_data=(X_validation, y_validation), epochs=30, batch_size=100, callbacks=[callbacks])
Epoch 1/30
34/34 [==============================] - 2s 69ms/step - loss: 2.4457 - accuracy: 0.1332 - val_loss: 2.4442 - val_accuracy: 0.1346
Epoch 2/30
34/34 [==============================] - 2s 57ms/step - loss: 2.4026 - accuracy: 0.1603 - val_loss: 2.2702 - val_accuracy: 0.2482
Epoch 3/30
34/34 [==============================] - 2s 57ms/step - loss: 2.0422 - accuracy: 0.2995 - val_loss: 1.7100 - val_accuracy: 0.3815
Epoch 4/30
34/34 [==============================] - 2s 58ms/step - loss: 1.7438 - accuracy: 0.3789 - val_loss: 1.5132 - val_accuracy: 0.4923
Epoch 5/30
34/34 [==============================] - 2s 57ms/step - loss: 1.5711 - accuracy: 0.4644 - val_loss: 1.2258 - val_accuracy: 0.5891
Epoch 6/30
34/34 [==============================] - 2s 58ms/step - loss: 1.4009 - accuracy: 0.5170 - val_loss: 1.1746 - val_accuracy: 0.6241
Epoch 7/30
34/34 [==============================] - 2s 57ms/step - loss: 1.2577 - accuracy: 0.5684 - val_loss: 1.0696 - val_accuracy: 0.6606
Epoch 8/30
34/34 [==============================] - 2s 58ms/step - loss: 1.1521 - accuracy: 0.6030 - val_loss: 1.0804 - val_accuracy: 0.6367
Epoch 9/30
34/34 [==============================] - 2s 58ms/step - loss: 1.0351 - accuracy: 0.6463 - val_loss: 0.9363 - val_accuracy: 0.7083
Epoch 10/30
34/34 [==============================] - 2s 58ms/step - loss: 0.9432 - accuracy: 0.6785 - val_loss: 0.8811 - val_accuracy: 0.7167
Epoch 11/30
34/34 [==============================] - 2s 58ms/step - loss: 0.8299 - accuracy: 0.7197 - val_loss: 0.8792 - val_accuracy: 0.6999
Epoch 12/30
34/34 [==============================] - 2s 58ms/step - loss: 0.7963 - accuracy: 0.7359 - val_loss: 0.9585 - val_accuracy: 0.7055
Epoch 13/30
34/34 [==============================] - 2s 58ms/step - loss: 0.8027 - accuracy: 0.7287 - val_loss: 0.9341 - val_accuracy: 0.7069
Epoch 14/30
34/34 [==============================] - 2s 58ms/step - loss: 0.7014 - accuracy: 0.7525 - val_loss: 0.7920 - val_accuracy: 0.7419
Epoch 15/30
34/34 [==============================] - 2s 58ms/step - loss: 0.6286 - accuracy: 0.7883 - val_loss: 0.8261 - val_accuracy: 0.7335
Epoch 16/30
34/34 [==============================] - 2s 58ms/step - loss: 0.5788 - accuracy: 0.7967 - val_loss: 0.8053 - val_accuracy: 0.7377
Epoch 17/30
34/34 [==============================] - 2s 58ms/step - loss: 0.5052 - accuracy: 0.8138 - val_loss: 0.7432 - val_accuracy: 0.7518
Epoch 18/30
34/34 [==============================] - 2s 58ms/step - loss: 0.4652 - accuracy: 0.8268 - val_loss: 0.7735 - val_accuracy: 0.7644
Epoch 19/30
34/34 [==============================] - 2s 59ms/step - loss: 0.4768 - accuracy: 0.8394 - val_loss: 0.8712 - val_accuracy: 0.7195
Epoch 20/30
34/34 [==============================] - 2s 58ms/step - loss: 0.4457 - accuracy: 0.8394 - val_loss: 0.8155 - val_accuracy: 0.7616
Epoch 21/30
34/34 [==============================] - 2s 58ms/step - loss: 0.4299 - accuracy: 0.8427 - val_loss: 0.7678 - val_accuracy: 0.7602
Epoch 22/30
34/34 [==============================] - 2s 59ms/step - loss: 0.3840 - accuracy: 0.8602 - val_loss: 0.9278 - val_accuracy: 0.7419
Epoch 23/30
34/34 [==============================] - 2s 59ms/step - loss: 0.3525 - accuracy: 0.8743 - val_loss: 0.7882 - val_accuracy: 0.7658
Epoch 24/30
34/34 [==============================] - 2s 59ms/step - loss: 0.3178 - accuracy: 0.8782 - val_loss: 0.7815 - val_accuracy: 0.7770
Epoch 25/30
34/34 [==============================] - 2s 59ms/step - loss: 0.3331 - accuracy: 0.8857 - val_loss: 0.7112 - val_accuracy: 0.7798
Epoch 26/30
34/34 [==============================] - 2s 59ms/step - loss: 0.3310 - accuracy: 0.8839 - val_loss: 0.7462 - val_accuracy: 0.7784
Epoch 27/30
34/34 [==============================] - 2s 59ms/step - loss: 0.2993 - accuracy: 0.8962 - val_loss: 0.7026 - val_accuracy: 0.7840
Epoch 28/30
34/34 [==============================] - 2s 59ms/step - loss: 0.3006 - accuracy: 0.8932 - val_loss: 0.9011 - val_accuracy: 0.7574
Epoch 29/30
34/34 [==============================] - 2s 58ms/step - loss: 0.2767 - accuracy: 0.8989 - val_loss: 0.8024 - val_accuracy: 0.7798
Epoch 30/30
34/34 [==============================] - 2s 58ms/step - loss: 0.2543 - accuracy: 0.9032 - val_loss: 0.7787 - val_accuracy: 0.7784
In [87]:
plt.plot(history.history['loss'])
plt.plot(history.history['val_loss'])
plt.xlabel('Epoch', fontsize=18)
plt.ylabel(r'Loss', fontsize=18)
plt.legend(('loss train','loss validation'), loc=0)
Out[87]:
<matplotlib.legend.Legend at 0x7f4aee3a5ac8>
In [88]:
# Print accuracy
plt.plot(history.history['accuracy'])
plt.plot(history.history['val_accuracy'])
plt.xlabel('Epoch', fontsize=18)
plt.ylabel(r'Loss', fontsize=18)
plt.legend(('accuracy train','accuracy validation'), loc=0)
Out[88]:
<matplotlib.legend.Legend at 0x7f4aedbe38d0>

Model Evaluation

In [89]:
loss, accuracy = model.evaluate(X_test, y_test)
print('Test loss: {:.2f} \n Test accuracy: {:.2f}'.format(loss, accuracy))

loss, accuracy = model.evaluate(X_train, y_train)
print('Train loss: {:.2f} \n Train accuracy: {:.2f}'.format(loss, accuracy))
23/23 [==============================] - 0s 10ms/step - loss: 0.7311 - accuracy: 0.8034
Test loss: 0.73 
 Test accuracy: 0.80
104/104 [==============================] - 1s 8ms/step - loss: 0.0902 - accuracy: 0.9672
Train loss: 0.09 
 Train accuracy: 0.97

Model is overfitting since training accuracy is 97% and testing accuracy is 80%. let's stop it before 23 epoch

Model Retrain

In [151]:
model1 = create_model(input_shape, num_classes)
optimizer = tf.keras.optimizers.Adam(learning_rate=0.001) # Optimizer

model1.compile(optimizer=optimizer,
              loss='categorical_crossentropy',
              metrics=['accuracy'])

model1.summary()
Model: "sequential_8"
_________________________________________________________________
Layer (type)                 Output Shape              Param #   
=================================================================
conv2d_32 (Conv2D)           (None, 128, 128, 32)      896       
_________________________________________________________________
max_pooling2d_24 (MaxPooling (None, 64, 64, 32)        0         
_________________________________________________________________
dropout_40 (Dropout)         (None, 64, 64, 32)        0         
_________________________________________________________________
conv2d_33 (Conv2D)           (None, 64, 64, 32)        9248      
_________________________________________________________________
conv2d_34 (Conv2D)           (None, 64, 64, 64)        18496     
_________________________________________________________________
max_pooling2d_25 (MaxPooling (None, 32, 32, 64)        0         
_________________________________________________________________
dropout_41 (Dropout)         (None, 32, 32, 64)        0         
_________________________________________________________________
conv2d_35 (Conv2D)           (None, 32, 32, 32)        18464     
_________________________________________________________________
max_pooling2d_26 (MaxPooling (None, 16, 16, 32)        0         
_________________________________________________________________
dropout_42 (Dropout)         (None, 16, 16, 32)        0         
_________________________________________________________________
flatten_8 (Flatten)          (None, 8192)              0         
_________________________________________________________________
dense_24 (Dense)             (None, 512)               4194816   
_________________________________________________________________
dropout_43 (Dropout)         (None, 512)               0         
_________________________________________________________________
dense_25 (Dense)             (None, 128)               65664     
_________________________________________________________________
dropout_44 (Dropout)         (None, 128)               0         
_________________________________________________________________
dense_26 (Dense)             (None, 12)                1548      
=================================================================
Total params: 4,309,132
Trainable params: 4,309,132
Non-trainable params: 0
_________________________________________________________________
In [152]:
history = model1.fit(X_train, y_train, validation_data=(X_validation, y_validation), epochs=21, callbacks=[callbacks])
Epoch 1/21
104/104 [==============================] - 2s 24ms/step - loss: 2.4447 - accuracy: 0.1167 - val_loss: 2.4139 - val_accuracy: 0.1346
Epoch 2/21
104/104 [==============================] - 2s 21ms/step - loss: 2.3221 - accuracy: 0.2144 - val_loss: 1.8567 - val_accuracy: 0.3548
Epoch 3/21
104/104 [==============================] - 2s 21ms/step - loss: 1.8214 - accuracy: 0.3759 - val_loss: 1.4875 - val_accuracy: 0.5007
Epoch 4/21
104/104 [==============================] - 2s 21ms/step - loss: 1.5137 - accuracy: 0.4839 - val_loss: 1.1760 - val_accuracy: 0.6241
Epoch 5/21
104/104 [==============================] - 2s 22ms/step - loss: 1.2691 - accuracy: 0.5735 - val_loss: 1.0317 - val_accuracy: 0.6424
Epoch 6/21
104/104 [==============================] - 2s 22ms/step - loss: 1.0888 - accuracy: 0.6292 - val_loss: 0.9214 - val_accuracy: 0.6985
Epoch 7/21
104/104 [==============================] - 2s 22ms/step - loss: 0.9954 - accuracy: 0.6635 - val_loss: 0.8901 - val_accuracy: 0.7139
Epoch 8/21
104/104 [==============================] - 2s 21ms/step - loss: 0.8681 - accuracy: 0.7053 - val_loss: 0.8145 - val_accuracy: 0.7461
Epoch 9/21
104/104 [==============================] - 2s 22ms/step - loss: 0.7947 - accuracy: 0.7221 - val_loss: 0.8507 - val_accuracy: 0.7195
Epoch 10/21
104/104 [==============================] - 2s 22ms/step - loss: 0.7450 - accuracy: 0.7477 - val_loss: 0.7767 - val_accuracy: 0.7489
Epoch 11/21
104/104 [==============================] - 2s 22ms/step - loss: 0.6858 - accuracy: 0.7618 - val_loss: 0.8096 - val_accuracy: 0.7237
Epoch 12/21
104/104 [==============================] - 2s 22ms/step - loss: 0.6025 - accuracy: 0.7964 - val_loss: 0.8035 - val_accuracy: 0.7546
Epoch 13/21
104/104 [==============================] - 2s 22ms/step - loss: 0.5373 - accuracy: 0.8084 - val_loss: 0.7757 - val_accuracy: 0.7672
Epoch 14/21
104/104 [==============================] - 2s 22ms/step - loss: 0.5365 - accuracy: 0.8096 - val_loss: 0.7732 - val_accuracy: 0.7461
Epoch 15/21
104/104 [==============================] - 2s 22ms/step - loss: 0.5047 - accuracy: 0.8232 - val_loss: 0.7315 - val_accuracy: 0.7770
Epoch 16/21
104/104 [==============================] - 2s 22ms/step - loss: 0.4719 - accuracy: 0.8373 - val_loss: 0.7909 - val_accuracy: 0.7728
Epoch 17/21
104/104 [==============================] - 2s 22ms/step - loss: 0.4024 - accuracy: 0.8526 - val_loss: 0.7489 - val_accuracy: 0.7868
Epoch 18/21
104/104 [==============================] - 2s 21ms/step - loss: 0.3715 - accuracy: 0.8665 - val_loss: 0.7939 - val_accuracy: 0.7728
Epoch 19/21
104/104 [==============================] - 2s 21ms/step - loss: 0.3573 - accuracy: 0.8734 - val_loss: 0.8688 - val_accuracy: 0.7560
Epoch 20/21
104/104 [==============================] - 2s 21ms/step - loss: 0.3376 - accuracy: 0.8818 - val_loss: 0.9529 - val_accuracy: 0.7658
Epoch 21/21
104/104 [==============================] - 2s 21ms/step - loss: 0.3288 - accuracy: 0.8839 - val_loss: 0.7623 - val_accuracy: 0.7896
In [154]:
loss, accuracy = model1.evaluate(X_test, y_test)
print('Test loss: {:.2f} \n Test accuracy: {:.2f}'.format(loss, accuracy))

loss, accuracy = model1.evaluate(X_train, y_train)
print('Train loss: {:.2f} \n Train accuracy: {:.2f}'.format(loss, accuracy))
23/23 [==============================] - 0s 8ms/step - loss: 0.7113 - accuracy: 0.7767
Test loss: 0.71 
 Test accuracy: 0.78
104/104 [==============================] - 1s 8ms/step - loss: 0.1437 - accuracy: 0.9405
Train loss: 0.14 
 Train accuracy: 0.94

The early stopping helping model to balance accuracy b/w test and training. Let's save the mode for future re-training

In [122]:
from keras.models import load_model
model1.save(save_extracted+'final_model.h5')
In [123]:
model1.load_weights(save_extracted+'final_model.h5')

Confusion matrix

In [135]:
y_pred = model.predict(X_test)
y_pred = (y_pred > 0.5) 
In [136]:
y_pred1 = model1.predict(X_test)
y_pred1 = (y_pred1 > 0.5) 
In [156]:
# Confusion Matrix for initial Model 
plt.subplots(figsize=(22,7)) #set the size of the plot 
def plot_confusion_matrix(cm, classes,
                          normalize=False,
                          title='Confusion matrix',
                          cmap=plt.cm.Blues):
    """
    This function prints and plots the confusion matrix.
    Normalization can be applied by setting `normalize=True`.
    """
    plt.imshow(cm, interpolation='nearest', cmap=cmap)
    plt.title(title)
    plt.colorbar()
    tick_marks = np.arange(len(classes))
    plt.xticks(tick_marks, classes, rotation=45)
    plt.yticks(tick_marks, classes)

    if normalize:
        cm = cm.astype('float') / cm.sum(axis=1)[:, np.newaxis]

    thresh = cm.max() / 2.
    for i, j in itertools.product(range(cm.shape[0]), range(cm.shape[1])):
        plt.text(j, i, cm[i, j],
                 horizontalalignment="center",
                 color="white" if cm[i, j] > thresh else "black")

    plt.tight_layout()
    plt.ylabel('True label')
    plt.xlabel('Predicted label')

# Predict the values from the validation dataset
Y_pred = model.predict(X_validation)
# Convert predictions classes to one hot vectors 
Y_pred_classes = np.argmax(Y_pred,axis = 1) 
# Convert validation observations to one hot vectors
Y_true = np.argmax(y_validation,axis = 1) 
# compute the confusion matrix
confusion_mtx = confusion_matrix(Y_true, Y_pred_classes) 
# plot the confusion matrix
plot_confusion_matrix(confusion_mtx, classes = range(12))
In [155]:
# Confusion Matrix for final Model1 
plt.subplots(figsize=(22,7)) #set the size of the plot 
def plot_confusion_matrix(cm, classes,
                          normalize=False,
                          title='Confusion matrix',
                          cmap=plt.cm.Blues):
    """
    This function prints and plots the confusion matrix.
    Normalization can be applied by setting `normalize=True`.
    """
    plt.imshow(cm, interpolation='nearest', cmap=cmap)
    plt.title(title)
    plt.colorbar()
    tick_marks = np.arange(len(classes))
    plt.xticks(tick_marks, classes, rotation=45)
    plt.yticks(tick_marks, classes)

    if normalize:
        cm = cm.astype('float') / cm.sum(axis=1)[:, np.newaxis]

    thresh = cm.max() / 2.
    for i, j in itertools.product(range(cm.shape[0]), range(cm.shape[1])):
        plt.text(j, i, cm[i, j],
                 horizontalalignment="center",
                 color="white" if cm[i, j] > thresh else "black")

    plt.tight_layout()
    plt.ylabel('True label')
    plt.xlabel('Predicted label')

# Predict the values from the validation dataset
Y_pred = model1.predict(X_validation)
# Convert predictions classes to one hot vectors 
Y_pred_classes = np.argmax(Y_pred,axis = 1) 
# Convert validation observations to one hot vectors
Y_true = np.argmax(y_validation,axis = 1) 
# compute the confusion matrix
confusion_mtx = confusion_matrix(Y_true, Y_pred_classes) 
# plot the confusion matrix
plot_confusion_matrix(confusion_mtx, classes = range(12))

Visualize predictions

for x_test[2], x_test[3], x_test[33], x_test[36], x_test[59].

In [159]:
y_pred1 = encoder.inverse_transform(y_pred1)

index = 2
plt.imshow(X_test[index], cmap='gray')
print("Predicted label:", y_pred1[index])
Predicted label: Loose Silky-bent
In [160]:
index = 3
plt.imshow(X_test[index], cmap='gray')
print("Predicted label:", y_pred1[index])
Predicted label: Common Chickweed
In [161]:
index = 33
plt.imshow(X_test[index], cmap='gray')
print("Predicted label:", y_pred1[index])
Predicted label: Black-grass
In [162]:
index = 36
plt.imshow(X_test[index], cmap='gray')
print("Predicted label:", y_pred1[index])
Predicted label: Black-grass
In [163]:
index = 59
plt.imshow(X_test[index], cmap='gray')
print("Predicted label:", y_pred1[index])
Predicted label: Small-flowered Cranesbill